<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>GIF 合成器</title>
    <style>
        /* 全局样式 */
        :root {
            --primary: #4361ee;
            --primary-light: #4895ef;
            --secondary: #3a0ca3;
            --success: #4cc9f0;
            --background: #f8f9fa;
            --card-bg: #ffffff;
            --text-primary: #212529;
            --text-secondary: #6c757d;
            --border-color: #dee2e6;
            --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
            --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
            --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.15);
            --radius-sm: 6px;
            --radius-md: 8px;
            --radius-lg: 12px;
            --transition: all 0.2s ease;
        }
        
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background-color: var(--background);
            color: var(--text-primary);
            padding: 20px;
            line-height: 1.6;
        }
        
        /* 容器样式 */
        .container {
            max-width: 1000px;
            margin: 0 auto;
            background: var(--card-bg);
            border-radius: var(--radius-lg);
            padding: 24px;
            box-shadow: var(--shadow-md);
        }
        
        /* 标题样式 */
        h1 {
            font-size: 28px;
            font-weight: 700;
            margin: 0 0 16px 0;
            color: var(--secondary);
            position: relative;
            display: inline-block;
        }
        
        h1:after {
            content: '';
            position: absolute;
            bottom: -4px;
            left: 0;
            width: 60px;
            height: 3px;
            background: linear-gradient(90deg, var(--primary), var(--primary-light));
            border-radius: 3px;
        }
        
        /* 行和列布局 */
        .row {
            display: flex;
            gap: 20px;
            align-items: stretch;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        
        .col {
            flex: 1;
            min-width: 0;
        }
        
        /* 拖放区域样式 */
        #dropArea {
            border: 2px dashed var(--border-color);
            padding: 32px 24px;
            border-radius: var(--radius-md);
            text-align: center;
            color: var(--text-secondary);
            cursor: pointer;
            background-color: rgba(67, 97, 238, 0.03);
            transition: var(--transition);
            position: relative;
            overflow: hidden;
        }
        
        #dropArea:hover, #dropArea.drag-over {
            border-color: var(--primary);
            background-color: rgba(67, 97, 238, 0.06);
            transform: translateY(-1px);
            box-shadow: var(--shadow-sm);
        }
        
        #dropArea:before {
            content: '';
            position: absolute;
            top: -2px;
            left: -2px;
            right: -2px;
            bottom: -2px;
            border-radius: var(--radius-md);
            background: linear-gradient(45deg, var(--primary), transparent, var(--primary-light));
            z-index: -1;
            opacity: 0;
            transition: opacity 0.3s ease;
        }
        
        #dropArea:hover:before {
            opacity: 1;
            animation: gradientBorder 3s ease infinite;
        }
        
        @keyframes gradientBorder {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
        
        /* 缩略图容器 */
        #thumbs {
            display: flex;
            flex-wrap: wrap;
            gap: 16px;
            margin-top: 20px;
            padding: 16px;
            background-color: rgba(0, 0, 0, 0.01);
            border-radius: var(--radius-md);
        }
        
        /* 单个缩略图样式 */
        .thumb {
            width: 140px;
            background: var(--card-bg);
            border: 1px solid var(--border-color);
            padding: 12px;
            border-radius: var(--radius-md);
            box-shadow: var(--shadow-sm);
            transition: var(--transition);
            position: relative;
            overflow: hidden;
        }
        
        .thumb:hover {
            transform: translateY(-3px);
            box-shadow: var(--shadow-md);
            border-color: var(--primary);
        }
        
        .thumb img {
            display: block;
            width: 100%;
            height: 90px;
            object-fit: cover;
            border-radius: var(--radius-sm);
            margin-bottom: 8px;
            transition: transform 0.3s ease;
        }
        
        .thumb:hover img {
            transform: scale(1.03);
        }
        
        .thumb .meta {
            font-size: 11px;
            font-weight: 500;
            margin-bottom: 8px;
            color: var(--text-primary);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        
        /* 延迟输入框样式 */
        .thumb input.frame-delay {
            width: 75px;
            padding: 4px 6px;
            font-size: 12px;
            border-radius: var(--radius-sm);
            border: 1px solid var(--border-color);
            transition: var(--transition);
            text-align: center;
        }
        
        .thumb input.frame-delay:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 2px rgba(67, 97, 238, 0.2);
        }
        
        /* 缩略图按钮样式 */
        .thumb button {
            font-size: 12px;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            border: none;
            background-color: var(--background);
            color: var(--text-secondary);
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            transition: var(--transition);
            margin: 0 2px;
        }
        
        .thumb button:hover {
            background-color: var(--primary);
            color: white;
            transform: scale(1.1);
        }
        
        .thumb button.rm:hover {
            background-color: #e63946;
        }
        
        /* 控制按钮区域 */
        .controls {
            display: flex;
            gap: 12px;
            align-items: center;
            flex-wrap: wrap;
        }
        
        /* 按钮样式 */
        .btn {
            padding: 10px 20px;
            border-radius: var(--radius-md);
            border: none;
            font-weight: 500;
            cursor: pointer;
            transition: var(--transition);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            font-size: 14px;
            position: relative;
            overflow: hidden;
        }
        
        .btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
            transition: left 0.5s ease;
        }
        
        .btn:hover::before {
            left: 100%;
        }
        
        .btn {
            background: linear-gradient(135deg, var(--primary), var(--primary-light));
            color: white;
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 16px rgba(67, 97, 238, 0.4);
        }
        
        .btn:active {
            transform: translateY(0);
        }
        
        .btn.secondary {
            background: var(--background);
            color: var(--text-primary);
            border: 1px solid var(--border-color);
        }
        
        .btn.secondary:hover {
            background: var(--border-color);
            transform: translateY(-1px);
        }
        
        /* 下载链接样式 */
        .btn.secondary {
            text-decoration: none;
        }
        
        /* 进度条样式 */
        #progressBar {
            height: 8px;
            background: var(--background);
            border-radius: 4px;
            overflow: hidden;
            box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
        }
        
        #progressInner {
            height: 100%;
            width: 0;
            background: linear-gradient(90deg, var(--primary), var(--success));
            transition: width 0.3s ease;
            position: relative;
        }
        
        #progressInner::after {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
            animation: progressShimmer 2s infinite;
        }
        
        @keyframes progressShimmer {
            0% { transform: translateX(-100%); }
            100% { transform: translateX(100%); }
        }
        
        /* 输入框样式 */
        input[type="number"] {
            padding: 10px 12px;
            border-radius: var(--radius-md);
            border: 1px solid var(--border-color);
            font-size: 14px;
            transition: var(--transition);
            width: 100%;
        }
        
        input[type="number"]:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 2px rgba(67, 97, 238, 0.2);
            transform: translateY(-1px);
        }
        
        /* 文本样式 */
        .small {
            font-size: 13px;
            color: var(--text-secondary);
            font-weight: 400;
        }
        
        /* 页脚样式 */
        footer {
            margin-top: 24px;
            padding-top: 16px;
            border-top: 1px solid var(--border-color);
            font-size: 12px;
            color: var(--text-secondary);
            text-align: center;
        }
        
        /* 结果区域 */
        #resultArea {
            margin-top: 20px;
            padding: 16px;
            background: var(--background);
            border-radius: var(--radius-md);
            text-align: center;
            transition: var(--transition);
        }
        
        #resultArea img {
            max-width: 100%;
            height: auto;
            border-radius: var(--radius-md);
            box-shadow: var(--shadow-md);
            margin-bottom: 16px;
            transition: transform 0.3s ease;
        }
        
        #resultArea img:hover {
            transform: scale(1.01);
        }
        
        /* 响应式设计 */
        @media (max-width: 768px) {
            .container {
                padding: 20px;
            }
            
            .row {
                flex-direction: column;
                align-items: stretch;
            }
            
            .thumb {
                width: calc(50% - 8px);
            }
            
            .btn {
                width: 100%;
            }
            
            #thumbs {
                padding: 12px;
                gap: 12px;
            }
        }
        
        @media (max-width: 480px) {
            body {
                padding: 12px;
            }
            
            .container {
                padding: 16px;
            }
            
            h1 {
                font-size: 24px;
            }
            
            .thumb {
                width: 100%;
            }
            
            #dropArea {
                padding: 24px 16px;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <h1>GIF 图片合成器</h1>
    <p class="small">支持多图上传、拖放排序、设置每帧延迟、生成并下载 GIF（完全在浏览器端）。</p>

    <div class="row">
        <div class="col">
            <div id="dropArea">点击或拖拽图片到这里（支持多选，支持 PNG/JPG/GIF）
                <div style="margin-top:8px;font-size:12px;color:#99a">建议宽高不超过 1600px，较大图片会影响生成速度</div>
            </div>
            <input id="fileInput" type="file" accept="image/*" multiple style="display:none" />
        </div>
        <div style="width:220px">
            <div style="margin-bottom:8px;" class="small">循环次数（0 = 无限）</div>
            <input id="loopInput" type="number" value="0" min="0" step="1" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ddd" />
        </div>
    </div>

    <div id="thumbs" aria-live="polite"></div>

    <div class="row" style="margin-top:14px">
        <div class="controls col">
            <button id="generateBtn" class="btn">生成 GIF</button>
            <button id="clearBtn" class="btn secondary">清空</button>
            <a id="downloadLink" class="btn secondary" style="display:none;text-decoration:none">下载 GIF</a>
        </div>
        <div style="width:320px">
            <div style="margin-bottom:6px">进度</div>
            <div id="progressBar"><div id="progressInner"></div></div>
        </div>
    </div>

    <div style="margin-top:12px">
        <canvas id="renderCanvas" style="display:none"></canvas>
        <div id="resultArea" style="margin-top:10px"></div>
    </div>

    <footer>注意：本工具完全基于浏览器端，图片不会上传到服务器。需要较新版本的 Chrome/Edge/Firefox。</footer>
</div>

<!-- 引入 gif.js（使用 CDN） -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js"></script>
<script>
    // 代码实现
    const dropArea = document.getElementById('dropArea');
    const fileInput = document.getElementById('fileInput');
    const thumbs = document.getElementById('thumbs');
    // 不再使用全局延迟输入框
    const loopInput = document.getElementById('loopInput');
    const generateBtn = document.getElementById('generateBtn');
    const clearBtn = document.getElementById('clearBtn');
    const progressInner = document.getElementById('progressInner');
    const downloadLink = document.getElementById('downloadLink');
    const resultArea = document.getElementById('resultArea');
    const canvas = document.getElementById('renderCanvas');

    let frames = []; // {file, dataURL, img, w, h, delay}

    function preventDefaults(e) {
        e.preventDefault();
        e.stopPropagation();
    }
    
    function highlight() {
        dropArea.classList.add('drag-over');
    }
    
    function unhighlight() {
        dropArea.classList.remove('drag-over');
    }
    
    ['dragenter', 'dragover'].forEach(evt => {
        dropArea.addEventListener(evt, highlight, false);
    });
    
    ['dragleave', 'drop'].forEach(evt => {
        dropArea.addEventListener(evt, unhighlight, false);
    });
    
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => {
        dropArea.addEventListener(evt, preventDefaults, false);
    });
    dropArea.addEventListener('drop', handleDrop, false);
    dropArea.addEventListener('click', () => fileInput.click());
    fileInput.addEventListener('change', (e) => handleFiles(e.target.files));

    function handleDrop(e){
        const dt = e.dataTransfer;
        const files = dt.files;
        handleFiles(files);
    }

    function handleFiles(fileList) {
        const arr = Array.from(fileList).filter(f => f.type.startsWith('image/'));
        if (!arr.length) return;
        
        // 初始延迟设为200ms，但仍可在每个帧单独修改
        const defaultDelay = 200;
        
        arr.forEach(file => {
            const reader = new FileReader();
            reader.onload = (ev) => {
                const dataURL = ev.target.result;
                const img = new Image();
                img.onload = () => {
                    frames.push({file, dataURL, img, w: img.width, h: img.height, delay: defaultDelay});
                    renderThumbs();
                };
                img.src = dataURL;
            };
            reader.readAsDataURL(file);
        });
    }

    function renderThumbs() {
        thumbs.innerHTML = '';
        frames.forEach((f, idx) => {
            const box = document.createElement('div');
            box.className = 'thumb';
            box.innerHTML = `
                <img src="${f.dataURL}" alt="frame-${idx}" />
                <div class="meta">
                    <span class="small">${f.file.name}</span>
                </div>
                <div style="margin-top:4px; display:flex; align-items:center; gap:4px;">
                    <span class="small">延迟:</span>
                    <input type="number" value="${f.delay}" min="20" step="10" class="frame-delay" data-idx="${idx}" style="width:70px; padding:2px 4px; font-size:12px; border-radius:3px; border:1px solid #ddd;" />
                    <span class="small">ms</span>
                </div>
                <div style="margin-top:6px; text-align:right;">
                    <button data-idx="${idx}" class="up">▲</button>
                    <button data-idx="${idx}" class="down">▼</button>
                    <button data-idx="${idx}" class="rm">✕</button>
                </div>`;
            thumbs.appendChild(box);
        });

        // 处理延迟输入框的变化
        thumbs.querySelectorAll('input.frame-delay').forEach(input => input.addEventListener('change', (e) => {
            const index = +e.target.dataset.idx;
            const val = Math.max(20, parseInt(e.target.value) || 20); // 最小20ms
            frames[index].delay = val;
            e.target.value = val; // 确保显示的是有效数值
        }));
        
        // attach handlers
        thumbs.querySelectorAll('button.up').forEach(btn => btn.addEventListener('click', (e) => {
            const index = +e.target.dataset.idx;
            if (index <= 0) return;
            [frames[index-1], frames[index]] = [frames[index], frames[index-1]];
            renderThumbs();
        }));
        
        thumbs.querySelectorAll('button.down').forEach(btn => btn.addEventListener('click', (e) => {
            const index = +e.target.dataset.idx;
            if (index >= frames.length-1) return;
            [frames[index+1], frames[index]] = [frames[index], frames[index+1]];
            renderThumbs();
        }));
        
        thumbs.querySelectorAll('button.rm').forEach(btn => btn.addEventListener('click', (e) => {
            const index = +e.target.dataset.idx;
            frames.splice(index, 1);
            renderThumbs();
        }));
    }

    clearBtn.addEventListener('click', () => {
        frames = [];
        renderThumbs();
        downloadLink.style.display = 'none';
        resultArea.innerHTML = '';
        progressInner.style.width = '0%';
    });

    generateBtn.addEventListener('click', async () => {
        if (!frames.length) {
            alert('请先添加至少一张图片');
            return;
        }
        generateBtn.disabled = true;
        generateBtn.textContent = '生成中...';
        progressInner.style.width = '0%';
        resultArea.innerHTML = '';

        // 不再使用全局延迟，每个帧使用自己的delay属性
        const loop = Math.max(0, parseInt(loopInput.value) || 0);

        // 统一画布尺寸：取所有帧最大宽高的最小边界（避免拉伸）
        const maxW = Math.max(...frames.map(f => f.w));
        const maxH = Math.max(...frames.map(f => f.h));
        const maxSize = 800; // 限制最大尺寸以避免占用过多内存
        let targetW = maxW, targetH = maxH;
        if (Math.max(targetW, targetH) > maxSize) {
            const ratio = maxSize / Math.max(targetW, targetH);
            targetW = Math.round(targetW * ratio);
            targetH = Math.round(targetH * ratio);
        }

        canvas.width = targetW;
        canvas.height = targetH;
        const ctx = canvas.getContext('2d');

        // 使用 gif.js
        const gif = new GIF({
            workers: 2,
            quality: 10,
            workerScript: './gif.worker.js',
            width: targetW,
            height: targetH
        });
        gif.setOptions({repeat: loop});

        for (let i = 0; i < frames.length; i++) {
            const f = frames[i];
            // 清空并绘制，保持居中缩放
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            const imgW = f.w, imgH = f.h;
            let drawW = imgW, drawH = imgH;
            const scale = Math.min(canvas.width / imgW, canvas.height / imgH, 1);
            drawW = Math.round(imgW * scale);
            drawH = Math.round(imgH * scale);
            const dx = Math.round((canvas.width - drawW) / 2);
            const dy = Math.round((canvas.height - drawH) / 2);
            ctx.drawImage(f.img, dx, dy, drawW, drawH);

            // 将当前画布作为帧添加，使用该帧的单独延迟时间
            gif.addFrame(ctx, {copy: true, delay: f.delay});
        }

        gif.on('progress', function(p) {
            progressInner.style.width = Math.round(p * 100) + '%';
        });

        gif.on('finished', function(blob) {
            const url = URL.createObjectURL(blob);
            const imgEl = document.createElement('img');
            imgEl.src = url;
            imgEl.style.maxWidth = '100%';
            imgEl.style.display = 'block';
            resultArea.appendChild(imgEl);
            downloadLink.href = url;
            downloadLink.download = 'animated.gif';
            downloadLink.style.display = 'inline-block';
            generateBtn.disabled = false;
            generateBtn.textContent = '生成 GIF';
        });

        // 开始渲染
        try {
            gif.render();
        } catch (err) {
            alert('生成失败：' + err.message);
            console.error(err);
            generateBtn.disabled = false;
            generateBtn.textContent = '生成 GIF';
        }
    });

</script>
</body>
</html>
